
package com.kweative.model.query;

import com.kweative.model.Table;
import com.kweative.model.column.Column;
import com.kweative.model.data.Row;
import com.kweative.model.data.RowSet;
import com.kweative.model.exception.ColumnNotFoundException;
import com.kweative.model.relation.Relation;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author henrik
 */
public class Select<T extends Table,R extends Row<T>> extends WhereStatement<Select<T,R>,T> {
    private Class<R> rowClass;
    private LinkedList<Column> cols = new LinkedList<Column>();
    private LinkedList<Select> unions = new LinkedList<Select>();

    public Select(T primTable) {
        super(primTable);
        rowClass = getTable().getRowClass();
    }

    public void setRowClass(Class<R> rowClass) {
        this.rowClass = rowClass;
    }
    
    /* Executer methods */
    public boolean exists() {
        limit(0,1);
        try {
            return getRows().size() > 0;
        } catch (SQLException ex) {
            Logger.getLogger(Select.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }
    public R first(){
        limit(0,1);
        RowSet rows;
        try {
            rows = getRows();
        } catch (SQLException ex) {
            Logger.getLogger(Select.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        if (rows.size() > 0)
            return (R) rows.get(0);
        return null;
    }
    private RowSet<R> getRows() throws SQLException {
        RowSet rowset = new RowSet();
        ResultSet rs = getTable().getConnection().createStatement().executeQuery(compile());
        ResultSetMetaData metaData = rs.getMetaData();
        int colCount = metaData.getColumnCount();
        try {
            while(rs.next()) {
                R r = rowClass.getConstructor(Table.class).newInstance(getTable());
                
                for(int i = 1; i <= colCount; i++) {
                    String colName = metaData.getColumnName(i);
                    Object val = rs.getObject(i);
                    
                    try {
                        Field field = r.getClass().getField(colName);
                        field.set(r, val);
                    } catch (Exception ex) {}
                }
                rowset.add(r);
            }
        } catch (Exception ex) {
            Logger.getLogger(Select.class.getName()).log(Level.SEVERE, null, ex);
        }
        return rowset;
    }
    public RowSet<R> all(int offset,int limit) throws SQLException {
        limit(offset, limit);
        return getRows();
    }
    public RowSet<R> all(int limit) throws SQLException {
        return all(0,limit);
    }
    public RowSet<R> all() throws SQLException {
        return all(-1,-1);
    }
    public RowSet<R> page(int page,int rowsPrPage) throws SQLException {
        limitPage(page, rowsPrPage);
        return getRows();
    }

    public Select<T,R> using(Relation<T,?> relation) {
        return (Select) relation.join((Select)this);
    }
    public Select<T,R> union(Select unionStmt) {
        unions.add(unionStmt);
        return this;
    }
    public Select<T,R> like(R row) {
        for(Field f: row.getClass().getDeclaredFields()) {
            try {
                Column col = getTable().getColumn(f.getName());
                where(col).eq(f.get(row));
            } catch (Exception ex) {
                Logger.getLogger(Insert.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return this;
    }
    public Select<T,R> columns(Column<?,T> ... selectCols) {
        return columnsFromArray(selectCols);
    }
    public Select<T,R> columnsFromArray(Column<?,T>[] selectCols) {
        if (selectCols.length == 0) return this;
        
        if (cols.size() == 0) {
            cols.add(getTable().__primary);
        }
        for(Column f:selectCols) {
            cols.add(f);
        }
        return this;
    }
    protected String compileColumnList() {
        if (cols.size() == 0) return String.format("%s.*",QueryUtil.formatTable(getTable()));
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for(Column f:cols) {
            if (!first) sb.append(",");
            sb.append(QueryUtil.formatColumn(f,true));
            first = false;
        }
        return sb.toString();
    }
    protected String compileUnions() {
        if (unions.size() == 0) return "";
        StringBuilder sb = new StringBuilder();

        for(Select stmt:unions) {
            sb.append("UNION ").append(stmt.compile());
        }
        return sb.toString();
    }
    @Override
    public String compile() {
        StringBuilder sb = new StringBuilder();
        try {
            String colList = compileColumnList();
            String wherePhrase = compileWherePhrase();
            String joinPhrase = compileJoinPhrase();
            String orderPhrase = compileOrderPhrase();
            String groupPhrase = compileGroupPhrase();
            String limitPhrase = compileLimitPhrase();

            sb.append(String.format("SELECT %s FROM %s",colList,QueryUtil.formatTable(getTable())));
            sb.append(joinPhrase);
            if (!wherePhrase.isEmpty())
                sb.append(" WHERE ").append(wherePhrase);
            sb.append(groupPhrase);
            sb.append(orderPhrase);
            sb.append(compileUnions());
            sb.append(limitPhrase);
        } catch (ColumnNotFoundException ex) {
            Logger.getLogger(Select.class.getName()).log(Level.SEVERE, null, ex);
        }
        return sb.toString();
    }
}
